
// <copyright file="CompositeDictionaryTests.cs" company="Subhadra Software Solutions">
//   
// </copyright>
// <summary>
//   The composite dictionary tests.
// </summary>

namespace SubhadraSolutions.Sharp.Utils.Tests.Collections.Concurrent
{
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Threading;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    using SubhadraSolutions.Sharp.Utils.Collections.Concurrent;

    /// <summary>
    /// The composite dictionary tests.
    /// </summary>
    [TestClass]
    public class CompositeDictionaryTests
    {
        #region Public Methods and Operators

        /// <summary>
        /// The add test.
        /// </summary>
        [TestMethod]
        public void AddTest()
        {
            this.performAddTest(false);
        }

        /// <summary>
        /// The add test multi thread.
        /// </summary>
        [TestMethod]
        public void AddTestMultiThread()
        {
            this.performAddTest(true);
        }

        /// <summary>
        /// The remove test.
        /// </summary>
        [TestMethod]
        public void RemoveTest()
        {
            this.performRemoveTest(false);
        }

        /// <summary>
        /// The remove test multi thread.
        /// </summary>
        [TestMethod]
        public void RemoveTestMultiThread()
        {
            this.performRemoveTest(true);
        }

        #endregion Public Methods and Operators

        #region Methods

        /// <summary>
        /// The build dictionary.
        /// </summary>
        /// <param name="start">
        /// The start.
        /// </param>
        /// <param name="count">
        /// The count.
        /// </param>
        /// <param name="multiThread">
        /// The multi thread.
        /// </param>
        /// <returns>
        /// The <see cref="CompositeDictionary"/>.
        /// </returns>
        private CompositeDictionary<char, int> buildDictionary(int start, int count, bool multiThread)
        {
            var dictionary = new CompositeDictionary<char, int>(multiThread);
            if (!multiThread)
            {
                for (int i = 0; i < count; i++)
                {
                    dictionary.AddOrUpdate(start + i, (start + i).ToString(CultureInfo.InvariantCulture).ToCharArray());
                }
            }
            else
            {
                const int parallelThreadCount = 100;
                this.performMultiThreadOperration(start, count, parallelThreadCount, delegate(object state)
                    {
                        var i = (int)state;
                        dictionary.AddOrUpdate(i, i.ToString(CultureInfo.InvariantCulture).ToCharArray());
                    });
            }

            return dictionary;
        }

        /// <summary>
        /// The perform add test.
        /// </summary>
        /// <param name="multiThread">
        /// The multi thread.
        /// </param>
        private void performAddTest(bool multiThread)
        {
            const int start = 0;
            const int count = 10000;

            CompositeDictionary<char, int> dictionary = this.buildDictionary(start, count, multiThread);
            bool isValid = this.validateAfterAdd(Enumerable.Range(start, count), dictionary);
            Assert.IsTrue(isValid);
        }

        /// <summary>
        /// The perform multi thread operration.
        /// </summary>
        /// <param name="start">
        /// The start.
        /// </param>
        /// <param name="count">
        /// The count.
        /// </param>
        /// <param name="numberOfThreads">
        /// The number of threads.
        /// </param>
        /// <param name="pts">
        /// The pts.
        /// </param>
        private void performMultiThreadOperration(int start, int count, int numberOfThreads, ParameterizedThreadStart pts)
        {
            for (int i = 0; i < count; i += numberOfThreads)
            {
                var threads = new List<Thread>();

                var e = new ManualResetEvent(false);
                for (int j = i; j < i + numberOfThreads && j < count; j++)
                {
                    var t = new Thread(delegate(object state)
                        {
                            e.WaitOne();
                            pts(state);
                        });
                    threads.Add(t);
                    t.Start(start + j);
                }

                e.Set();
                foreach (Thread thread in threads)
                {
                    thread.Join();
                }
            }
        }

        /// <summary>
        /// The perform multi thread operration decending.
        /// </summary>
        /// <param name="start">
        /// The start.
        /// </param>
        /// <param name="count">
        /// The count.
        /// </param>
        /// <param name="numberOfThreads">
        /// The number of threads.
        /// </param>
        /// <param name="pts">
        /// The pts.
        /// </param>
        private void performMultiThreadOperrationDecending(int start, int count, int numberOfThreads, ParameterizedThreadStart pts)
        {
            for (int i = count - 1; i >= 0; i -= numberOfThreads)
            {
                var threads = new List<Thread>();

                var e = new ManualResetEvent(false);
                for (int j = i; j > i - 100 && j >= 0; j--)
                {
                    var t = new Thread(delegate(object state)
                        {
                            e.WaitOne();
                            pts(state);
                        });
                    threads.Add(t);
                    t.Start(start + j);
                }

                e.Set();
                foreach (Thread thread in threads)
                {
                    thread.Join();
                }
            }
        }

        /// <summary>
        /// The perform remove.
        /// </summary>
        /// <param name="dictionary">
        /// The dictionary.
        /// </param>
        /// <param name="i">
        /// The i.
        /// </param>
        private void performRemove(CompositeDictionary<char, int> dictionary, int i)
        {
            char[] keys = i.ToString(CultureInfo.InvariantCulture).ToCharArray();
            int value = -1;
            if (!dictionary.TryGetValue(out value, keys))
            {
                Assert.Fail("Item does not exist");
            }
            else
            {
                if (value != i)
                {
                    Assert.Fail("Value in the dictionary is other than the expected");
                }
                else
                {
                    dictionary.RemoveIfExists(keys);
                    if (dictionary.TryGetValue(out value, keys))
                    {
                        Assert.Fail("Item exists even after removing it");
                    }
                }
            }
        }

        /// <summary>
        /// The perform remove test.
        /// </summary>
        /// <param name="multiThread">
        /// The multi thread.
        /// </param>
        private void performRemoveTest(bool multiThread)
        {
            const int start = 0;
            const int count = 10000;
            CompositeDictionary<char, int> dictionary = this.buildDictionary(start, count, multiThread);
            bool isValid = this.validateAfterAdd(Enumerable.Range(start, count), dictionary);
            this.performRemoveTest(dictionary, start, count, multiThread);
        }

        /// <summary>
        /// The perform remove test.
        /// </summary>
        /// <param name="dictionary">
        /// The dictionary.
        /// </param>
        /// <param name="start">
        /// The start.
        /// </param>
        /// <param name="count">
        /// The count.
        /// </param>
        /// <param name="multiThread">
        /// The multi thread.
        /// </param>
        private void performRemoveTest(CompositeDictionary<char, int> dictionary, int start, int count, bool multiThread)
        {
            if (!multiThread)
            {
                for (int i = count - 1; i >= 0; i--)
                {
                    this.performRemove(dictionary, start + i);
                }
            }
            else
            {
                const int parallelThreadCount = 100;
                this.performMultiThreadOperrationDecending(start, count, parallelThreadCount, delegate(object state)
                    {
                        var i = (int)state;
                        this.performRemove(dictionary, i);
                    });
            }
        }

        /// <summary>
        /// The validate after add.
        /// </summary>
        /// <param name="numbers">
        /// The numbers.
        /// </param>
        /// <param name="dictionary">
        /// The dictionary.
        /// </param>
        /// <returns>
        /// The <see cref="bool"/>.
        /// </returns>
        private bool validateAfterAdd(IEnumerable<int> numbers, CompositeDictionary<char, int> dictionary)
        {
            foreach (int i in numbers)
            {
                int value = -1;
                if (!dictionary.TryGetValue(out value, i.ToString(CultureInfo.InvariantCulture).ToCharArray()))
                {
                    return false;
                }

                if (value != i)
                {
                    return false;
                }
            }

            return true;
        }

        #endregion Methods
    }
}
